#!/bin/sh -efu
#
# Copyright (C) 2006-2021  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2006-2020  Alexey Gladkov <legion@altlinux.org>
# Copyright (C) 2006  Sergey Vlasov <vsu@altlinux.org>
# Copyright (C) 2008  Alexey I. Froloff <raorn@altlinux.org>
#
# gear common shell functions.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

if [ -z "${__included_gear_sh_functions-}" ]; then
__included_gear_sh_functions=1

. shell-error
. shell-quote
. shell-args

PROG="${0##*/}"
PROG_VERSION='@VERSION@'

gear_short_options='r:,t:,h,q,v,V'
gear_long_options='no-compress,commit,bzip2,gzip,lz4,lzop,xz,zstd,disable-specsubst,command,hasher,rpmbuild,update-spec,export-dir:,describe,rules:,tree-ish:,help,quiet,verbose,version'

__cleanup_handler_name=
__cleanup_handler()
{
	trap - EXIT
	[ -z "$__cleanup_handler_name" ] ||
		"$__cleanup_handler_name" "$@" ||:
	exit "$@"
}

__exit_handler()
{
	__cleanup_handler $?
}

__signal_handler()
{
	__cleanup_handler 1
}

install_cleanup_handler()
{
	__cleanup_handler_name="${1-}"
	trap __exit_handler EXIT
	trap __signal_handler HUP PIPE INT QUIT TERM
}

uninstall_cleanup_handler()
{
	trap - EXIT HUP PIPE INT QUIT TERM
	__cleanup_handler_name=
}

# set specified variable to the value stored in another specified variable
# 1st arg is the name of variable that should be assigned,
# 2ns arg is the name of variable that contains the value to be assigned.
set_var_value()
{
	eval "$1=\"\${$2-}\""
}

lineno=
rules=
rules_error()
{
	local lineno_text=
	[ -z "$lineno" ] || lineno_text=" line $lineno"
	fatal "$rules$lineno_text: $*"
}

rules_info()
{
	local lineno_text=
	[ -z "$lineno" ] || lineno_text=" line $lineno"
	message "$rules$lineno_text: $*"
}

: ${__gear_exported_vars:=__gear_exported_vars}
export __gear_exported_vars
gear_export_vars()
{
	export "$@"
	__gear_exported_vars="$__gear_exported_vars $*"
}

gear_unset_exported_vars()
{
	unset $__gear_exported_vars
}

is_hex_sha1()
{
	[ "${#1}" -eq 40 ] || return 1

	# "${1##*[![:xdigit:]]*}" does not work in ash
	[ -n "${1##*[!0123456789abcdefABCDEF]*}" ] || return 1

	return 0
}

# Change the current working directory to the top-level directory
# of the git working tree if the former is a subdirectory of the latter.
gear_saved_working_directory=''
chdir_to_toplevel()
{
	local cdup
	cdup="$(git rev-parse --show-cdup)"
	if [ -z "$cdup" ]; then
		gear_saved_working_directory=''
	else
		gear_saved_working_directory="$PWD"
		cd "$cdup" ||
			fatal "Cannot chdir to $cdup, the toplevel of the working tree"
	fi
}

# Change the current working directory back if it was changed earlier
# by chdir_to_toplevel.
chdir_back_from_toplevel()
{
	[ -z "$gear_saved_working_directory" ] ||
		cd "$gear_saved_working_directory"
}

get_object_sha1()
{
	local name="$1"; shift
	local sha1

	sha1="$(git rev-parse --verify "$name")" || return 1

	# Verify that we really got a SHA-1 (git rev-parse --verify accepts
	# things like ^COMMIT and returns ^SHA1 for them).
	is_hex_sha1 "$sha1" || return 1

	printf %s "$sha1"
}

get_tag_sha1()
{
	local name="$1"; shift
	local sha1

	sha1="$(get_object_sha1 "$name")" || return
	[ "$(git cat-file -t "$sha1")" = tag ] || return

	printf %s "$sha1"
}

get_commit_sha1()
{
	local name="$1"; shift
	local sha1

	sha1="$(get_object_sha1 "$name")" || return 1
	git rev-parse --verify "$sha1^0" || return 1
}

get_tree_sha1()
{
	local name="$1"; shift
	local sha1

	sha1="$(get_object_sha1 "$name")" || return 1
	git rev-parse --verify "$sha1^{tree}" || return 1
}

tree_entry_name()
{
	local tree="$1"; shift
	local path="$1"; shift

	if [ -z "${path#.}" ]; then
		printf %s "$tree"
	elif [ -z "${tree##*:*}" ]; then
		printf %s "$tree/$path"
	else
		printf %s "$tree:$path"
	fi
}

traverse_tree()
{
	local tree="$1"; shift
	local path="$1"; shift
	local optional="$1"; shift
	local id

	id="$(tree_entry_name "$tree" "$path")"
	if git cat-file tree "$id" >/dev/null 2>&1; then
		printf %s "$id"
	elif [ "$optional" = 1 ]; then
		return 2
	else
		rules_error "tree \"$path\" not found in \"$tree\""
	fi
}

# fetch blob by id+name.
cat_blob()
{
	local tree="$1"; shift
	local name="$1"; shift
	local id

	id="$(tree_entry_name "$tree" "$name")"
	git cat-file blob "$id" ||
		rules_error "blob \"$name\" not found in \"$tree\""
}

# Check that given variable contains non-empty path without directory
# traversal.
check_pathname()
{
	local name="$1"; shift
	local value="$1"; shift

	[ -n "$value" ] ||
		rules_error "Empty $name \"$value\" specified"

	[ "$value" != '..' ] &&
	    [ -n "${value##/*}" ] &&
	    [ -n "${value##../*}" ] &&
	    [ -n "${value%%*/..}" ] &&
	    [ -n "${value##*/../*}" ] ||
		rules_error "Invalid $name \"$value\" specified"
}

# Check that given variable contains non-empty file name without directory
# separators or directory traversal.
check_filename()
{
	local name="$1"; shift
	local value="$1"; shift

	[ -n "$value" ] ||
		rules_error "Empty $name \"$value\" specified"

	[ "$value" != '..' ] && [ -n "${value##*/*}" ] ||
		rules_error "Invalid $name \"$value\" specified"
}

rules_cleanup()
{
	if [ -s "$1" ]; then
		echo >> "$1"
		sed -i 's/^[[:space:]]*\([^[:space:]:]\+\)[[:space:]]*:[[:space:]]*/\1: /' "$1"
	fi
}

workdir=
cleanup_workdir()
{
	[ -z "${workdir-}" ] || {
		rm -rf -- "$workdir"
		workdir=
	}
}

main_tree_id=

gear_find_rules_check_tree()
{
	[ -n "$(git ls-tree "$main_tree_id" "$1")" ]
}

gear_find_rules_check_cwd()
{
	[ -s "$1" ]
}

gear_find_rules_cat_tree()
{
	cat_blob "$main_tree_id" "$rules"
}

gear_find_rules_cat_cwd()
{
	cat -- "$rules"
}

gear_find_rules_common()
{
	local check_rules="$1"; shift
	local cat_rules="$1"; shift

	if [ -f "$workdir/rules" ]; then
		return 0
	fi
	local r=
	if [ -n "$rules" ]; then
		if $check_rules "$rules"; then
			r="$rules"
		fi
	else
		local n
		for n in .gear-rules .gear/rules; do
			if $check_rules "$n"; then
				rules="$n"
				r="$rules"
				break
			fi
		done
	fi
	if [ -n "$r" ]; then
		$cat_rules
	fi >"$workdir/rules"
	rules_cleanup "$workdir/rules"
	[ -n "$rules" ] || rules='.gear/rules'
}

find_rules_in_tree()
{
	gear_find_rules_common gear_find_rules_check_tree gear_find_rules_cat_tree
}

find_rules_in_cwd()
{
	gear_find_rules_common gear_find_rules_check_cwd gear_find_rules_cat_cwd
}

get_uniq_directive_from_rules()
{
	local var_name="$1"; shift
	local directive="$1"; shift
	local value=

	set_var_value "$var_name" value

	find_rules_in_tree
	[ -s "$workdir/rules" ] || return 0

	value="$(sed -rn "s/^$directive:[[:space:]]+([^[:space:]].*)/\\1/p" "$workdir/rules" |
		sed 's/[[:space:]]\+$//')"
	[ -n "$value" ] || return 0

	[ "`printf %s "$value" |wc -l`" -le 0 ] ||
		rules_error "More than one \"$directive\" directive specified"

	set_var_value "$var_name" value
}

get_uniq_filename_from_rules()
{
	local var_name="$1"; shift
	local directive="$1"; shift
	local filename=

	set_var_value "$var_name" filename

	get_uniq_directive_from_rules filename "$directive" || return
	[ -n "$filename" ] || return 0

	check_pathname "$directive" "$filename"

	set_var_value "$var_name" filename
}

expand_rpm_macros()
{
	local spec="$1"; shift
	local var_name="$1"; shift
	local var_value

	set_var_value var_value "$var_name"
	[ -n "$var_value" ] && [ -z "${var_value##*%*}" ] || return 0

	var_value="$(awk '
	BEGIN {
		macros["nil"] = ""
	}
	match($0, /^%(define|global)[[:space:]]+([^[:space:]]+)[[:space:]]+(.*)$/, f) {
		macros[f[2]] = expand(f[3])
	}
	END {
		print expand(value)
	}
	function expand(input,
			output, pos, macro, f)
	{
		output = ""
		while ((pos = index(input, "%")) != 0) {
			output = output substr(input, 1, pos - 1)
			input = substr(input, pos + 1)
			if (input ~ /^%/) {
				output = output "%"
				input = substr(input, 2)
				continue
			}
			if (match(input, /^([[:alpha:]_][[:alnum:]_]*)(.*)$/, f) ||
			    match(input, /^\{([[:alpha:]_][[:alnum:]_]*)\}(.*)$/, f)) {
				macro = f[1]
				if (macro in macros) {
					output = output macros[macro]
					input = f[2]
					continue
				}
			}
			output = output "%"
		}
		return output input
	}
	' value="$var_value" "$spec" )"

	set_var_value "$var_name" var_value
}

spec_name=
spec_epoch=
spec_version=
spec_release=
get_NVR_from_spec()
{
	local spec="$1"; shift

	spec_name="$(sed '/^name:[[:space:]]*/I!d;s///;q' "$spec" |sed 's/[[:space:]]\+$//')"
	spec_epoch="$(sed '/^\(epoch\|serial\):[[:space:]]*/I!d;s///;q' "$spec" |sed 's/[[:space:]]\+$//')"
	spec_version="$(sed '/^version:[[:space:]]*/I!d;s///;q' "$spec" |sed 's/[[:space:]]\+$//')"
	spec_release="$(sed '/^release:[[:space:]]*/I!d;s///;q' "$spec" |sed 's/[[:space:]]\+$//')"

	expand_rpm_macros "$spec" spec_name
	expand_rpm_macros "$spec" spec_epoch
	expand_rpm_macros "$spec" spec_version
	expand_rpm_macros "$spec" spec_release
}

substitute_vars_in_spec()
{
	[ -n "${specsubst-}" ] || return 0

	local tag spec opts var qvar value qvalue
	tag="$workdir/main_tag"
	spec="$workdir/specfile"

	git cat-file tag "$main_tag_sha1" > "$tag"

	quote_shell_args opts "$specsubst"
	eval "set -- $opts"

	for var; do
		quote_sed_regexp_variable qvar "$var"
		if grep -qs "^X-gear-specsubst:[[:space:]]\\+$qvar=" "$tag"; then
			value="$(sed "/^X-gear-specsubst:[[:space:]]\\+$qvar=/I!d;s///;q" "$tag")"
		elif grep -qs "^X-girar-specsubst:[[:space:]]\\+$qvar=" "$tag"; then
			value="$(sed "/^X-girar-specsubst:[[:space:]]\\+$qvar=/I!d;s///;q" "$tag")"
		else
			fatal "specsubst variable \"$var\" is not defined in tag \"$main_tag_id\""
		fi
		quote_sed_regexp_variable qvalue "$value"
		sed -i "s/@$qvar@/$qvalue/g" -- "$spec"
	done
}

specsubst=
disable_specsubst=
get_specsubst_from_rules()
{
	if [ -z "$disable_specsubst" ]; then
		get_uniq_directive_from_rules specsubst specsubst
		if [ -n "$specsubst" ]; then
			[ -z "$(printf %s "$specsubst" |LANG=C tr -d '[:alnum:]_[:space:]')" ] ||
				rules_error "Invalid specsubst value \"$specsubst\" specified"
		fi
	fi
}

specfile=
pkg_name=
pkg_epoch=
pkg_version=
pkg_release=
find_specfile()
{
	local fail_ok="${1-}"

	get_specsubst_from_rules
	if [ -n "$specsubst" ]; then
		if [ -z "${main_tag_sha1-}" ]; then
			[ "$fail_ok" = fail_ok ] && specsubst= ||
			fatal 'specsubst directive requires a tag'
		fi
	fi

	# first try specfile defined in $rules if any.
	get_uniq_filename_from_rules specfile spec

	# second try specfile in toplevel tree.
	if [ -z "$specfile" ]; then
		specfile="$(git ls-tree "$main_tree_id" |
			sed -ne 's/^[^[:space:]]\+[[:space:]]\+blob[[:space:]]\+[^[:space:]]\+[[:space:]]\+\([^/[:space:]]\+\.spec\)$/\1/p')"
		[ "`printf %s "$specfile" |wc -l`" -le 0 ] || {
			message "Too many specfiles found${GIT_DIR:+ in $GIT_DIR}"
			[ "$fail_ok" = fail_ok ] && return 1 || exit
		}
	fi
	[ -n "$specfile" ] || {
		[ "$fail_ok" = fail_ok ] && return 1 ||
			fatal "No specfiles found${GIT_DIR:+ in $GIT_DIR}"
	}
	cat_blob "$main_tree_id" "$specfile" >"$workdir/specfile"

	substitute_vars_in_spec

	local spec_name spec_epoch spec_version spec_release
	get_NVR_from_spec "$workdir/specfile"
	pkg_name="$spec_name"
	pkg_epoch="$spec_epoch"
	pkg_version="$spec_version"
	pkg_release="$spec_release"
}

tag_dir=
gear_find_tags_check_tree()
{
	local t="$1"; shift
	local id type

	id="$(tree_entry_name "$main_tree_id" "$t/list")"
	type="$(git cat-file -t "$id" 2>/dev/null)" || type=
	if [ "$type" = 'blob' ]; then
		tag_dir="$t"
		return 0
	fi
	return 1
}

gear_find_tags_check_cwd()
{
	local t="$1"; shift
	if [ -s "$t/list" ]; then
		tag_dir="$t"
		return 0
	fi
	return 1
}

gear_find_tags_cat_tree()
{
	cat_blob "$main_tree_id" "$tag_dir/list"
}

gear_find_tags_cat_cwd()
{
	cat -- "$tag_dir/list"
}

tag_dir_default=
gear_find_tags_common()
{
	local check_tags="$1"; shift
	local cat_tags="$1"; shift

	# first try tag directory defined in rules if any.
	get_uniq_filename_from_rules tag_dir tags

	# second try tag directory in toplevel tree.
	if [ -z "$tag_dir" ]; then
		if [ -n "$tag_dir_default" ]; then
			if $check_tags "$tag_dir_default"; then
				:
			fi
		else
			local t
			for t in .gear-tags .gear/tags; do
				if $check_tags "$t"; then
					break
				fi
			done
		fi
	fi

	if [ -n "$tag_dir" ]; then
		$cat_tags
	fi >"$workdir/tags"
	[ -n "$tag_dir" ] || tag_dir='.gear/tags'
}

find_tags_in_tree()
{
	gear_find_tags_common gear_find_tags_check_tree gear_find_tags_cat_tree
}

find_tags_in_cwd()
{
	gear_find_tags_common gear_find_tags_check_cwd gear_find_tags_cat_cwd
}

tag_list_lineno=
tag_list_error()
{
	local lineno_text=
	[ -z "$tag_list_lineno" ] || lineno_text=" line $tag_list_lineno"
	fatal "$tag_dir/list$lineno_text: $*"
}

lookup_tag()
{
	local tag_list_file="$1"; shift
	local requested_tag_name="$1"; shift
	local variable="$1"; shift
	local sha1 name

	tag_list_lineno=0
	while read -r sha1 name; do
		tag_list_lineno="$((tag_list_lineno+1))"
		if [ "$name" = "$requested_tag_name" ]; then
			set_var_value "$variable" sha1
			return 0
		fi
	done <"$tag_list_file"

	return 1
}

extract_stored_tag_chain()
{
	local sha1="$1"; shift
	local id type real_sha1 next

	id="$(tree_entry_name "$main_tree_id" "$tag_dir/$sha1")"
	type="$(git cat-file -t "$id" 2>/dev/null)" || type=
	if [ "$type" = "blob" ]; then
		next="$(git cat-file blob "$id" | sed -ne '1s/^object \(.*\)$/\1/p')" ||
			tag_list_error "Bad stored tag $sha1: parse failed"
		is_hex_sha1 "$next" ||
			tag_list_error "Bad stored tag $sha1: invalid format"
		real_sha1="$(git cat-file blob "$id" | git hash-object -t tag -w --stdin)" ||
			tag_list_error "Bad stored tag $sha1: extract failed"
		[ "$real_sha1" = "$sha1" ] ||
			tag_list_error "Bad stored tag $sha1: hash mismatch (got $real_sha1)"
		extract_stored_tag_chain "$next"
	elif [ -n "$type" ]; then
		tag_list_error "Bad stored tag $sha1: type=$type"
	else
		printf %s "$sha1"
	fi
}

is_ancestor_commit()
{
	local ancestor="$1"; shift
	local descendant="$1"; shift
	local bases

	bases="$(git log -n1 --pretty=format:1 ^"$descendant" "$ancestor")" ||
		return 1
	[ -z "$bases" ] || return 1
}

check_tag_name()
{
	local name="$1"; shift

	[ -n "$name" ] ||
		rules_error 'Empty tag name is not allowed'
	[ -n "${name##/*}" ] ||
		rules_error "Invalid tag name \"$name\": initial '/' is not allowed"
	[ -n "${name%%*/}" ] ||
		rules_error "Invalid tag name \"$name\": trailing '/' is not allowed"
	[ -n "${name##*[][*?^~:@[:space:]]*}" ] ||
		rules_error "Invalid tag name \"$name\": invalid characters found"
	git check-ref-format "tags/$name" ||
		rules_error "Invalid tag name \"$name\""
}

main_commit_sha1=
resolve_commit_name()
{
	local name="$1"; shift
	local result found_commit type final_sha1

	if is_hex_sha1 "$name"; then
		result="$name"
		found_commit="$name"
	else
		check_tag_name "$name"
		lookup_tag "$workdir/tags" "$name" result ||
			rules_error "Name \"$name\" not found in tag list"
		is_hex_sha1 "$result" ||
			tag_list_error "Invalid SHA-1 \"$result\" for name \"$name\""
		found_commit="$(extract_stored_tag_chain "$result")" ||
			tag_list_error "Broken tag chain for name \"$name\""
	fi

	type="$(git cat-file -t "$found_commit")" ||
		rules_error "Name \"$name\" specifies a nonexistent object $found_commit"
	if [ "$type" = "tag" ]; then
		rules_error "Tag object $found_commit for name \"$name\" is not stored"
	elif [ "$type" != "commit" ]; then
		rules_error "Name \"$name\" specifies a $type, not a commit"
	fi

	# Clean up possible upper/lower case differences in raw SHA-1 ids
	final_sha1="$(git rev-parse --verify "$found_commit")" ||
		rules_error "Name \"$name\" resolved to invalid object ID \"$found_commit\""

	# Ensure than $final_sha1 is an ancestor of $main_commit_sha1
	is_ancestor_commit "$final_sha1" "$main_commit_sha1" ||
		rules_error "Name \"$name\" resolved to $final_sha1, which is not an ancestor of commit $main_commit_sha1"

	printf %s "$result"
}

resolve_tree_base_name()
{
	local name="$1"; shift

	if [ "$name" = '.' ]; then
		printf %s "$main_tree_id"
	else
		resolve_commit_name "$name"
	fi
}

parse_tree_path()
{
	local name="$1"; shift
	local value="$1"; shift
	local path

	[ -n "$value" ] ||
		rules_error "Empty $name specified"

	if [ -z "${value##*:*}" ]; then
		[ -n "${value%%:*}" ] ||
			rules_error "Empty commit name in $name specified"
		path="${value#*:}"
		[ -n "$path" ] ||
			rules_error "Empty path in $name specified"
	else
		path="$value"
		value=".:$value"
	fi
	check_pathname "$name" "$path"
	printf %s "$value"
}

def_spec_pattern='*.spec'
spec_pattern="$def_spec_pattern"
find_specfile_in_cwd()
{
	local prefix="$1"; shift
	local pattern f spec='' many_specs=''

	for pattern in ${spec_pattern}; do
		for f; do
			[ -z "${f##$pattern}" ] || [ -z "${f%%$pattern}" ] ||
				continue
			[ ! -L "$f" ] && [ -f "$f" ] ||
				continue
			[ -z "$spec" ] ||
				many_specs=1
			spec="$f"
		done
		[ -z "$spec" ] ||
			break
	done
	if [ -z "$spec" ]; then
		message "${prefix}Spec file not found."
	elif [ -n "$many_specs" ]; then
		message "${prefix}Too many spec files - ignored all."
		spec=
	fi

	if [ -n "$spec" ]; then
		printf %s "$spec"
		return 0
	else
		return 1
	fi
}

guess_specfile_from_checkout()
{
	local rules="${RULES-}"
	local workdir __cleanup_handler_name

	install_cleanup_handler cleanup_workdir
	workdir="$(mktemp -dt "$PROG.XXXXXXXXXX")"

	find_rules_in_cwd
	get_uniq_filename_from_rules specfile spec
	[ -s "$specfile" ] || specfile=

	cleanup_workdir
	uninstall_cleanup_handler
}

guess_specfile_from_tree()
{
	local rules="${RULES-}"
	local main_tree_id="${TREE_ID:-HEAD}"
	local workdir __cleanup_handler_name

	git cat-file tree "$main_tree_id" >/dev/null 2>&1 || return 0

	install_cleanup_handler cleanup_workdir
	workdir="$(mktemp -dt "$PROG.XXXXXXXXXX")"

	find_specfile fail_ok ||:

	cleanup_workdir
	uninstall_cleanup_handler
}

# specfile guess logic:
#
# If specfile is not specified, then
#   if TREE_ID is specified, then
#     first try the rules-based guess from the tree,
#     next try the rules-based guess the checkout;
#   otherwise
#     first try the rules-based guess from checkout,
#     next try the rules-based guess from HEAD.
# If nothing of the above helped to find a specfile, then
#   try a wild guess from the checkout.
#
guess_specfile()
{
	if [ -z "$specfile" ]; then
		if [ -n "${TREE_ID-}" ]; then
			guess_specfile_from_tree
		else
			guess_specfile_from_checkout
		fi
	fi

	if [ -z "$specfile" ]; then
		if [ -n "${TREE_ID-}" ]; then
			guess_specfile_from_checkout
		else
			guess_specfile_from_tree
		fi
	fi

	if [ -z "$specfile" ]; then
		specfile="$(set +f; find_specfile_in_cwd '' *)" ||:
	fi
}

run_command()
{
	verbose "Executing: $*"
	"$@"
}

subst_key()
{
	local var_name="$1"; shift
	local key_name="$1"; shift
	local key_value="$1"; shift
	local var_value quoted

	set_var_value var_value "$var_name"

	if [ "$var_value" != "${var_value#*$key_name*}" ]; then
		check_filename "$key_name" "$key_value"
		quote_sed_regexp_variable quoted "$key_value"
		var_value="$(printf %s "$var_value" |sed "s/$key_name/$quoted/g")"
		set_var_value "$var_name" var_value
	fi
}

subst_key_in_vars()
{
	local key_name="$1"; shift
	local key_value="$1"; shift
	local variable

	for variable; do
		subst_key "$variable" "$key_name" "$key_value"
	done
}

subst_spec_keywords()
{
	local name="$1"; shift
	local version="$1"; shift
	local release="$1"; shift

	subst_key_in_vars '@name@' "$name" "$@"
	subst_key_in_vars '@version@' "$version" "$@"
	subst_key_in_vars '@release@' "$release" "$@"
}

subst_NVR_from_spec_file()
{
	local spec="$1"; shift
	local spec_name='' spec_version='' spec_release=''

	if [ -n "$spec" ]; then
		get_NVR_from_spec "$spec"
	fi
	[ -n "$spec_name" ] || spec_name="$pkg_name"
	[ -n "$spec_version" ] || spec_version="$pkg_version"
	[ -n "$spec_release" ] || spec_release="$pkg_release"

	subst_spec_keywords "$spec_name" "$spec_version" "$spec_release" "$@"
}

gear_config_option()
{
	local var_name="$1"; shift
	local opt_name="$1"; shift
	local def_val="$1"; shift
	local subcmd gear_config_option_val

	subcmd=".${PROG#gear-}"
	[ "$subcmd" != '.gear' ] || subcmd=
	gear_config_option_val="$(git config --get "gear$subcmd.$opt_name")" ||
		gear_config_option_val="$def_val"

	set_var_value "$var_name" gear_config_option_val
}

valid_string()
{
	[ -z "$(printf %s "$1" |LANG=C tr -d '[:graph:]')" ]
}

fi #__included_gear_sh_functions
